#include <functional>
#include <memory>
#include <unordered_map>
#include <fcntl.h>
#include "epoll.hpp"
#include "socket.hpp"

using namespace std; 

// 设置fd为非阻塞
void set_nonblocking(int fd) {
    int flags = fcntl(fd, F_GETFL);
    if (flags == -1) {
        lg(ERROR, "set_nonblocking errorstr: %s", strerror(errno));
        exit(1);
    }
    flags |= O_NONBLOCK;
    if (fcntl(fd, F_SETFL, flags) == -1) {
        lg(ERROR, "set_nonblocking errorstr: %s", strerror(errno));
        exit(1);
    }
    lg(DEBUG, "set_nonblocking success");
}


class connection;
class server;

using func_t = function<void(shared_ptr<connection>)>;

// 最好要对每一个连接都管理起来
// 各自有一个套接字，和对应的的缓冲区等等
class connection {
public:
    connection(int con_fd, func_t rf, func_t wf, func_t ef, shared_ptr<server> sptr) 
    : _con_fd(con_fd), _read_cb(rf), _write_cb(wf), _excption_cb(ef), _sptr(sptr)
    {}

    int get_fd() const {
        return _con_fd;
    }

    string& get_inbuffer() {
        return _inbuffer;
    }

    string& get_outbuffer() {
        return _outbuffer;
    }

private:
    int _con_fd;
    string _inbuffer;
    string _outbuffer;
    
public:
    // 设置io回调，待事件就绪时调用
    func_t _read_cb;
    func_t _write_cb;
    func_t _excption_cb;

    shared_ptr<server> _sptr;
};

class server {
    const size_t default_event_array_size = 1024;
    const size_t default_buffer_size = 1024;
public:
    server(uint16_t port, func_t handle_msg) 
        : _port(port)
        , _handle_msg(handle_msg)
    {}

    void init()  {
        _lis_sk.socket();
        set_nonblocking(_lis_sk.get_sockfd());
        _lis_sk.bind(_port);
        _lis_sk.listen();

        _epoll.epoll_crt();
        lg(INFO, "server initialized, NON_BLOCK");
    }
    

    void start() {
        // 把监听套接字放入epoll模型
        // ET模式要设置EPOLLET
        add_connection(_lis_sk.get_sockfd(), EPOLLIN | EPOLLET, bind(&server::accpter, this, placeholders::_1), 
            nullptr, bind(&server::exceptioner, this, placeholders::_1), 
            make_shared<server>(_port, _handle_msg));

        while (true) {
            int nfds = _epoll.epoll_wait(-1);
            if(nfds < 0) {
                lg(ERROR, "epoll_wait errorstr: %s", strerror(errno));
                break;
            }
            else if(nfds == 0) {
                lg(DEBUG, "epoll_wait timeout");
                continue;
            }
            else {
                dispatcher(nfds);
            }
        }
    }

    void add_connection(int fd, int flags, func_t rf, func_t wf, func_t ef, shared_ptr<server> ths) {
        shared_ptr<connection> conn = make_shared<connection>(fd, rf, wf, ef, ths);
        _connections.insert(make_pair(fd, conn));
        
        _epoll.epoll_ctl(EPOLL_CTL_ADD, fd, flags);
        lg(INFO, "add connection success, connected fd is %d", fd);
    }

    void dispatcher(int nfds) {
        const struct epoll_event *events = _epoll.get_ready_event_array();
        for(int i = 0; i < nfds; ++i) {
            int fd = events[i].data.fd;
            int evts = events[i].events;
            if(evts & EPOLLIN) {
                _connections[fd]->_read_cb(_connections[fd]);
            }
            if(evts & EPOLLOUT) {
                _connections[fd]->_write_cb(_connections[fd]);
            }
        }
    }

    void accpter(shared_ptr<connection> conn) {
        while(true) {
            int newfd = _lis_sk.accept();
            if(newfd < 0) {
                if(newfd == EINTR) {
                    continue;;
                }
                else {
                    // EWOULDBLOCK || -1
                    break;
                }
            }
            else {
                set_nonblocking(newfd);
                add_connection(newfd, EPOLLIN | EPOLLET, bind(&server::reader, this, placeholders::_1), 
                    bind(&server::writer, this, placeholders::_1), bind(&server::exceptioner, this, placeholders::_1), 
                    make_shared<server>(_port, _handle_msg));
            }
        }
    }

    void reader(shared_ptr<connection> conn) {
        char buffer[default_buffer_size];
        while(true) {
            ssize_t nread = read(conn->get_fd(), buffer, sizeof(buffer) - 1);
            if(nread == -1) {
                // 在非阻塞模式下，如果文件描述符没有数据可读，read函数会立即返回-1，并设置errno为EAGAIN或EWOULDBLOCK
                if(errno == EAGAIN || errno == EWOULDBLOCK) {
                    break;
                }
                else if(errno == EINTR) {
                    continue;
                }
                else {
                    lg(ERROR, "read errorstr: %s", strerror(errno));
                    conn->_excption_cb(conn);
                    return;
                }
            }
            else if(nread == 0) {
                lg(INFO, "client close, fd is %d", conn->get_fd());
                conn->_excption_cb(conn);
                return;
            }
            else {
                buffer[nread] = '\0';
                conn->get_inbuffer().append(buffer);
            }
        }
        _handle_msg(conn);
    }

    void writer(shared_ptr<connection> conn) {
        string &out_buff = conn->get_outbuffer();
        lg(INFO, "out_buff: %s", out_buff.c_str());
        while(true) {
            ssize_t nwrite = write(conn->get_fd(), out_buff.c_str(), out_buff.size());
            if(nwrite == -1) {
                if(errno == EWOULDBLOCK) {
                    break;
                }
                else if(errno == EINTR) {
                    continue;
                }
                else {
                    lg(ERROR, "write errorstr: %s", strerror(errno));
                    conn->_excption_cb(conn);
                    return;
                }
            }
            else if(nwrite == 0) {
                return;
            }
            else {
                out_buff.erase(0, nwrite);
                if(out_buff.empty()) {
                    break;
                }
            }
        }
        if(_connections.count(conn->get_fd()) != 0) {
        // 判断是否需要长关心
            if(!out_buff.empty()) {
                _epoll.epoll_ctl(EPOLL_CTL_MOD, conn->get_fd(), EPOLLIN | EPOLLOUT | EPOLLET);
            }
            else {
                _epoll.epoll_ctl(EPOLL_CTL_MOD, conn->get_fd(), EPOLLIN | EPOLLET); // ?
            }
        }
    }

    void exceptioner(shared_ptr<connection> conn) {
        int close_fd = conn->get_fd();
        _epoll.epoll_ctl(EPOLL_CTL_DEL, close_fd);
        _connections.erase(close_fd);
        close(close_fd);
        lg(INFO, "connection %d exception, close it", close_fd);
    }

private:
    Socket _lis_sk;
    Epoll _epoll;
    uint16_t _port;
    // 组织每个连接对象
    unordered_map<int, shared_ptr<connection>> _connections;

    func_t _handle_msg;
};